using System;
using System.Collections.Generic;
using System.Net;
using System.Threading;

using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using SmartBoard.Core.BaseObjects;

namespace SmartBoard.Core.Networking
{
    public partial class SocketClientSync: Disposable
    {
        #region Fields

        private event OnSymmetricAuthenticateEvent OnSymmetricAuthenticateEvent;
        private event OnSSLClientAuthenticateEvent OnSSLClientAuthenticateEvent;
        private event OnDisconnectEvent OnDisconnectedEvent;

        private SocketClient _socketClient;
        private ISocketConnection _socketConnection;
        private SocketClientSyncSocketService _socketClientEvents;
        private SocketClientSyncCryptService _cryptClientEvents;

        private readonly int _connectTimeout;
        private bool _isConnected;
        private object _isConnectedSync;

        private readonly int _sentTimeout;

        private Queue<byte[]> _receivedQueue;

        private AutoResetEvent _receivedEvent;
        private AutoResetEvent _connectEvent;
        private AutoResetEvent _sentEvent;
        private AutoResetEvent _exceptionEvent;
        private ManualResetEvent _disconnectEvent;
        #endregion
        
        #region Constructor

        public SocketClientSync(IPEndPoint remoteEndpoint):
            this(remoteEndpoint, DelimiterType.None, null, CompressionType.None, EncryptionType.None, 4096, 2048)
        { }

        public SocketClientSync(IPEndPoint remoteEndpoint, DelimiterType delimiterType, byte[] delimiter, CompressionType compressionType, EncryptionType encryptionType, int socketBufferSize, int messageBufferSize)
        {
            _receivedEvent = new AutoResetEvent(false);
            _exceptionEvent = new AutoResetEvent(false);
            _sentEvent = new AutoResetEvent(false);
            _connectEvent = new AutoResetEvent(false);
            _disconnectEvent = new ManualResetEvent(false);

            _receivedQueue = new Queue<byte[]>();

            _connectTimeout = 10000;
            _sentTimeout = 10000;

            _isConnectedSync = new object();
            _isConnected = false;

            _socketClientEvents = new SocketClientSyncSocketService(this);
            _cryptClientEvents = new SocketClientSyncCryptService(this);

            RemoteEndPoint = remoteEndpoint;

            //----- Message Types!
            EncryptionType = encryptionType;
            CompressionType = compressionType;
            DelimiterType = delimiterType;

            //----- Proxy!
            ProxyInfo = null;

            //----- Socket delimiter and buffer size!
            Delimiter = delimiter;

            MessageBufferSize = messageBufferSize;
            SocketBufferSize = socketBufferSize;
        }

        #endregion

        #region Properties

        public IPEndPoint RemoteEndPoint
        {
            get;
            private set;
        }

        public DelimiterType DelimiterType
        {
            get;
            private set;
        }

        public EncryptionType EncryptionType
        {
            get;
            private set;
        }

        public CompressionType CompressionType
        {
            get;
            private set;
        }

        public byte[] Delimiter
        {
            get;
            private set;
        }

        public ProxyInfo ProxyInfo
        {
            get;
            set;
        }

        public int MessageBufferSize
        {
            get;
            private set;
        }

        public int SocketBufferSize
        {
            get;
            private set;
        }

        public bool Connected
        {
            get
            {
                bool connected;
                lock (_isConnectedSync)
                {
                    connected = _isConnected;
                }
                return connected;
            }
            private set
            {
                lock (_isConnectedSync)
                {
                    _isConnected = value;
                }
            }
        }

        public Exception LastException
        {
            get;
            private set;
        }
        #endregion

        #region Methods

        public void Connect()
        {
            if (IsDisposed) return;
            if (Connected) return;

            LastException = null;

            _connectEvent.Reset();
            _exceptionEvent.Reset();
            _disconnectEvent.Reset();

            _socketClient = new SocketClient(CallbackThreadType.WorkerThread, _socketClientEvents, DelimiterType, Delimiter, SocketBufferSize, MessageBufferSize);

            _socketClient.AddConnector("SocketClientSync", RemoteEndPoint, ProxyInfo, EncryptionType, CompressionType, _cryptClientEvents);

            var wait = new WaitHandle[]
                           {
                               _connectEvent, 
                               _exceptionEvent
                           };

            _socketClient.Start();

            var signal = WaitHandle.WaitAny(wait, _connectTimeout, false);
            switch (signal)
            {
                case 0:
                    //----- Connect!
                    LastException = null;
                    Connected = true;
                    break;
                case 1:
                    //----- Exception!
                    Connected = false;
                    _socketConnection = null;
                            
                    _socketClient.Stop();
                    _socketClient.Dispose();
                    _socketClient = null;
                    break;
                default:
                    //----- TimeOut!
                    LastException = new TimeoutException("Connect timeout.");

                    Connected = false;
                    _socketConnection = null;
                    _socketClient.Stop();
                    _socketClient.Dispose();
                    _socketClient = null;
                    break;
            }
        }

        public void Write(byte[] buffer)
        {
            if (IsDisposed) return;
            if (!Connected) return;

            _sentEvent.Reset();
            _exceptionEvent.Reset();
            
            LastException = null;
            var wait = new WaitHandle[] { _sentEvent, _disconnectEvent, _exceptionEvent };

            _socketConnection.BeginSend(buffer);
            var signaled = WaitHandle.WaitAny(wait, _sentTimeout, false);

            switch (signaled)
            {
                case 0:
                    //----- Sent!
                    LastException = null;
                    break;
                case 1:
                    //----- Disconnected!
                    DoDisconnect();
                    break;
                case 2:
                    //----- Exception!
                    break;
                default:
                    //----- TimeOut!
                    LastException = new TimeoutException("Write timeout.");
                    break;
            }
        }

        public byte[] Read(int timeOut)
        {
            if (IsDisposed) return null;
            if (!Connected) return null;

            LastException = null;
            byte[] result = null;
            lock (_receivedQueue)
            {
                if (_receivedQueue.Count > 0)
                {
                    result = _receivedQueue.Dequeue();
                }
            }

            if (result == null)
            {
                var wait = new WaitHandle[] {_receivedEvent, _disconnectEvent, _exceptionEvent};
                var signaled = WaitHandle.WaitAny(wait, timeOut, false);
                switch (signaled)
                {
                    case 0:
                        //----- Received!
                        lock (_receivedQueue)
                        {
                            if (_receivedQueue.Count > 0)
                            {
                                result = _receivedQueue.Dequeue();
                            }
                        }
                        LastException = null;
                        break;
                    case 1:
                        //----- Disconnected!
                        DoDisconnect();
                        break;
                    case 2:
                        //----- Exception!
                        break;
                    default:
                        //----- TimeOut!
                        LastException = new TimeoutException("Read timeout.");
                        break;
                }
            }
            return result;
        }

        public void Disconnect()
        {
            if (IsDisposed) return;
            if (!Connected) return;

            LastException = null;
            _exceptionEvent.Reset();
            
            if (_socketConnection == null) return;
            var wait = new WaitHandle[]
                           {
                               _disconnectEvent, 
                               _exceptionEvent
                           };

            _socketConnection.BeginDisconnect();
            var signaled = WaitHandle.WaitAny(wait, _connectTimeout, false);

            switch (signaled)
            {
                case 0:
                    DoDisconnect();
                    break;
                case 1:
                    //----- Exception!
                    DoDisconnect();
                    break;
                default:
                    //----- TimeOut!
                    LastException = new TimeoutException("Disconnect timeout.");
                    break;
            }
        }
        #endregion

        #region Public Events

        public event OnDisconnectEvent OnDisconnected
        {
            add { OnDisconnectedEvent += value; }
            remove { OnDisconnectedEvent -= value; }
        }

        public event OnSymmetricAuthenticateEvent OnSymmetricAuthenticate
        {
            add { OnSymmetricAuthenticateEvent += value; }
            remove { OnSymmetricAuthenticateEvent -= value; }
        }

        public event OnSSLClientAuthenticateEvent OnSSLClientAuthenticate
        {
            add { OnSSLClientAuthenticateEvent += value; }
            remove { OnSSLClientAuthenticateEvent -= value; }
        }
        #endregion

        #region Private Methods

        private void DoOnSSLClientAuthenticate(ISocketConnection connection, out string serverName, ref X509Certificate2Collection certs, ref bool checkRevocation)
        {
            serverName = String.Empty;

            if (OnSSLClientAuthenticateEvent != null)
            {
                OnSSLClientAuthenticateEvent(connection, out serverName, ref certs, ref checkRevocation);
            }
        }

        private void DoOnSymmetricAuthenticate(ISocketConnection connection, out RSACryptoServiceProvider serverKey)
        {
            serverKey = new RSACryptoServiceProvider();
            serverKey.Clear();

            if (OnSymmetricAuthenticateEvent != null)
            {
                OnSymmetricAuthenticateEvent(connection, out serverKey);
            }
        }

        private void Enqueue(byte[] data)
        {
            if (IsDisposed) return;

            lock (_receivedQueue)
            {
                _receivedQueue.Enqueue(data);
                _receivedEvent.Set();
            }
        }

        private void DoDisconnect()
        {
            var fireEvent = false;

            lock (_isConnectedSync)
            {
                if (_isConnected)
                {
                    //----- Disconnect!
                    _isConnected = false;
                    _socketConnection = null;

                    if (_socketClient != null)
                    {
                        _socketClient.Stop();
                        _socketClient.Dispose();
                        _socketClient = null;
                    }
                    fireEvent = true;
                }
            }

            if ((OnDisconnectedEvent != null) && fireEvent)
            {
                OnDisconnectedEvent();
            }
        }
        #endregion

        #region Destructor

        protected override void Free(bool canAccessFinalizable)
        {

            _socketConnection = null;
            _socketClientEvents = null;
            _cryptClientEvents = null;
            _isConnectedSync = null;
            LastException = null;

            if (_receivedQueue != null)
            {
                _receivedQueue.Clear();
                _receivedQueue = null;
            }

            if (_socketClient != null)
            {
                _socketClient.Stop();
                _socketClient.Dispose();
                _socketClient = null;
            }

            if (_exceptionEvent != null)
            {
                _exceptionEvent.Close();
                _exceptionEvent = null;
            }

            if (_connectEvent != null)
            {
                _connectEvent.Close();
                _connectEvent = null;
            }

            if (_sentEvent != null)
            {
                _sentEvent.Close();
                _sentEvent = null;
            }

            if (_receivedEvent != null)
            {
                _receivedEvent.Close();
                _receivedEvent = null;
            }

            if (_disconnectEvent != null)
            {
                _disconnectEvent.Close();
                _disconnectEvent = null;
            }

            base.Free(canAccessFinalizable);

        }
        #endregion
    }
}